/*
 * Individual.java
 *
 *  
 */
package org.msb.finance.data;

import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

/**
 * The {@code Individual} class represents individuals whose financial situation is being managed by the application in
 * a given {@link Cabinet} object.
 * 
 * {@code Individual} objects allow transactions to be associated with a person. This allows the application to separate
 * transactions when preparing reports (especially useful for tax reports).
 * 
 * @author Marc Boudreau
 */
public class Individual implements Comparable<Individual> {

	/*
	 * The abbreviated name of the individual that is used in the GUI to select this individual.
	 */
	private String shortName;

	/*
	 * The full name including middle initial for the individual.
	 */
	private String fullName;

	/*
	 * The date of birth for the individual. The time portion of the Date object is always midnight. This field is used
	 * to calculate the individual's age.
	 */
	private Date dateOfBirth;

	/*
	 * The individual's social insurance number. This is for reference only.
	 */
	private String sin;

	/*
	 * The individual's spouse if he or she has one. The relationship is always reciprocated.
	 */
	private Individual spouse;

	/*
	 * The individual's dependents.
	 */
	private Set<Individual> dependents;

	/**
	 * Constructs a new {@code Individual} instance.
	 */
	public Individual() {
		this.dependents = new HashSet<Individual>(3);
	}

	/**
	 * Returns the short name for the person represented by this {@code Individual} object. The short name is typically,
	 * the person's first name and will be used in the GUI when selecting from a list of defined individuals.
	 * 
	 * @return A {@link String} object containing the short name.
	 */
	public String getShortName() {
		return this.shortName;
	}

	/**
	 * Changes the short name for this instance to the provided value.
	 * 
	 * @param name
	 *            A {@link String} object containing the new value for the short name field.
	 */
	public void setShortName(String name) {
		this.shortName = name;
	}

	/**
	 * Returns the full name for the person represented by this {@code Individual} object. The full name is kept for
	 * reference purpose only.
	 * 
	 * @return A {@link String} object containing the full name.
	 */
	public String getFullName() {
		return this.fullName;
	}

	/**
	 * Changes the full name for this instance to the provided value.
	 * 
	 * @param name
	 *            A {@link String} object containing the new value for the full name field.
	 */
	public void setFullName(String name) {
		this.fullName = name;
	}

	/**
	 * Returns the date of birth for the person represented by this {@code Individual} object. The value is returned in
	 * a {@link Date} object. The time portion of the {@code Date} object will always be set to midnight.
	 * 
	 * @return A {@code Date} object containing the date of birth in the date portion and midnight in the time portion.
	 */
	public Date getDateOfBirth() {
		return (Date) this.dateOfBirth.clone();
	}

	/**
	 * Sets the date of birth for the person represented by this {@code Individual} object to the provided value. In
	 * order to ensure that the time portion of the {@code dateOfBirth} field is always midnight, this method will reset
	 * the hours, minutes, seconds, and milliseconds on the provided object.
	 * 
	 * @param date
	 *            A {@link Date} object to use to set the date of birth.
	 */
	public void setDateOfBirth(Date date) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		cal.set(Calendar.HOUR, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);

		this.dateOfBirth = cal.getTime();
	}

	/**
	 * Calculates the age of the person represented by this {@code Individual} object. The age is calculated as of the
	 * date provided by the {@link Date} object. If the provided {@code Date} object is {@code null}, then today's date
	 * is used.
	 * 
	 * @param asOfDate
	 *            A {@code Date} object representing the date from which the age is calculated.
	 * 
	 * @return The person's age in whole years.
	 */
	public short getAge(Date asOfDate) {
		Date date = asOfDate;
		if (null == date) {
			date = new Date();
		}

		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		short curYear = (short) cal.get(Calendar.YEAR);
		short curMonth = (short) cal.get(Calendar.MONTH);
		short curDay = (short) cal.get(Calendar.DAY_OF_MONTH);

		cal.setTime(this.dateOfBirth);
		short dobYear = (short) cal.get(Calendar.YEAR);
		short dobMonth = (short) cal.get(Calendar.MONTH);
		short dobDay = (short) cal.get(Calendar.DAY_OF_MONTH);

		short age = (short) (curYear - dobYear);
		if (curMonth < dobMonth || (curMonth == dobMonth && curDay < dobDay)) {
			age--;
		}

		return age;
	}

	/**
	 * Returns the Social Insurance Number for the person represented by this {@code Individual} object.
	 * 
	 * @return A {@link String} object containing the Social Insurance Number.
	 */
	public String getSin() {
		return this.sin;
	}

	/**
	 * Sets the Social Insurance Number for the person represented by this {@code Individual} object to the provided
	 * value.
	 * 
	 * @param sin
	 *            A {@link String} object containing the social insurance number to set.
	 */
	public void setSin(String sin) {
		this.sin = sin;
	}

	/**
	 * Returns the {@code Individual} object that represents the spouse of the person represented by this
	 * {@code Individual} object, if this person has a spouse; otherwise {@code null} is returned. The
	 * {@code Individual} instance returned by this method is guaranteed to return this instance from its
	 * {@code #getSpouse()} method.
	 * 
	 * @return The {@code Individual} object associated as the spouse.
	 */
	public Individual getSpouse() {
		return this.spouse;
	}

	/**
	 * Sets a reference to an {@code Individual} object as the spouse for the person represented by this
	 * {@code Individual} object. Because the spouse relationship is bidirectional, this method will reciprocate the
	 * change to the {@code Individual} object provided as the spouse. Furthermore, if this instance, or the provided
	 * instance already had a spouse, the relationship is first unlinked.
	 * 
	 * @param spouse
	 *            The {@code Individual} object to associated as the spouse.
	 */
	public void setSpouse(Individual spouse) {
		if (this.equals(spouse)) {
			throw new IllegalArgumentException("Cannot set an Individual's spouse to itself."); //$NON-NLS-1$
		}

		if (null != this.spouse) {
			if (!this.spouse.equals(spouse)) {
				this.spouse.spouse = null;
			} else {
				/*
				 * Attempting to set this Individual's spouse to the same object.
				 */
				return;
			}
		}

		if (null != spouse) {
			if (null != spouse.spouse) {
				if (!spouse.spouse.equals(this)) {
					spouse.spouse.spouse = null;
				}
			}
		}

		this.spouse = spouse;
		if (null != spouse) {
			spouse.spouse = this;
		}
	}

	/**
	 * 
	 * @return
	 */
	public Set<Individual> getDependents() {
		return new HashSet<Individual>(this.dependents);
	}

	/**
	 * 
	 * @param dependent
	 */
	public void addDependent(Individual dependent) {
		this.dependents.add(dependent);
	}

	/**
	 * 
	 * @param dependent
	 */
	public void removeDependent(Individual dependent) {
		this.dependents.remove(dependent);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	public int compareTo(Individual o) {
		if (this == o) {
			return 0;
		}

		if (null == this.fullName) {
			return null == o.fullName ? 0 : -1;
		} else if (null == o.fullName) {
			return 1;
		}

		return this.fullName.compareTo(o.fullName);
	}
}
